/*
 * Copyright (c) 2011, Nikolaus Moll
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * * Neither the name of the gradecalculator.net nor the
 *   names of its contributors may be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */
package net.gradecalculator.plugin.core.srdx;

import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import net.gradecalculator.core.GCCore;
import net.gradecalculator.core.interfaces.ICoreFactory;
import net.gradecalculator.core.interfaces.ICourse;
import net.gradecalculator.core.interfaces.ICourseOfStudiesIdentifier;
import net.gradecalculator.core.interfaces.ICourseType;
import net.gradecalculator.core.interfaces.IElectiveCoursesArea;
import net.gradecalculator.core.interfaces.IElectiveModulesArea;
import net.gradecalculator.core.interfaces.IModule;
import net.gradecalculator.core.interfaces.IModuleList;
import net.gradecalculator.core.interfaces.IModuleListItem;
import net.gradecalculator.core.interfaces.IPerformanceType;
import net.gradecalculator.core.interfaces.IStudyRegulations;
import net.gradecalculator.core.interfaces.IUniversity;
import net.gradecalculator.core.util.CourseListUtil;
import net.gradecalculator.core.util.PerformanceListUtil;
import net.gradecalculator.plugin.core.interfaces.IRegulationsPlugin;
import net.gradecalculator.plugin.core.srdx.internal.IXmlModuleListItem;
import net.gradecalculator.plugin.core.srdx.internal.XmlCourse;
import net.gradecalculator.plugin.core.srdx.internal.XmlIndividualModule;
import net.gradecalculator.plugin.core.srdx.internal.XmlModule;
import net.gradecalculator.plugin.core.srdx.internal.XmlModuleList;
import net.gradecalculator.plugin.core.srdx.internal.XmlSelectableModuleLists;
import net.gradecalculator.plugin.core.srdx.internal.XmlStudyRegulationsDescription;
import net.gradecalculator.plugin.core.srdx.internal.XmlUniversity;
import net.gradecalculator.plugin.core.srdx.internal.enums.XmlCourseTypeDescriptor;
import net.gradecalculator.plugin.core.srdx.internal.enums.XmlExaminationTypeDescriptor;
import net.gradecalculator.plugin.loader.exceptions.PluginException;

public class SrdxPlugin implements IRegulationsPlugin {

	private static final String DIRUNIVERSITY = "resources/universities";
	private static final String DIRSPODESCRIPTION = "resources/universities";
	private static final String EXTUNIVERSITY = ".unixml";
	private static final String EXTSPODESCRIPTION = ".srdx";
	
	private final Map<String, List<String>> extensions;

	private ICoreFactory factory;
	
	private String root = System.getenv("user.dir");
	
	@SuppressWarnings("serial")
	public SrdxPlugin()
	{
		this.extensions = new HashMap<String, List<String>>();
		extensions.put("Study regulations XML file", new LinkedList<String>() {{ add("srdx"); }});
	}
	
	private String getUniversityFilename(final String university)
	{
		return root + File.separator + DIRUNIVERSITY + File.separator + university + EXTUNIVERSITY;
	}
	
	private String getRegulationsFilename(final String regulations) {
		return root + File.separator + DIRSPODESCRIPTION + File.separator + regulations + EXTSPODESCRIPTION;		
	}
	
	
	@Override
	public void setCoreFactory(final ICoreFactory factory) {
		this.factory = factory;
	}

	@Override
	public IUniversity readUniversity(final String universityName) {
		try {
			final String filename;
			if (universityName.endsWith(EXTUNIVERSITY)) {
				filename = universityName;
			} else {
				filename = DIRUNIVERSITY + File.separator + universityName + EXTUNIVERSITY;
			}
			
			final IUniversity result = factory.createUniversity();
			final XmlUniversity university = XmlUniversity.createFromXML(filename);

			result.setName(university.getName());
			result.setCountryCode(university.getCountryCode());
		
			return result;
		}
		catch (final Exception e) {
			throw new PluginException("Cannot read university", e);
		}
	}

	protected static List<IPerformanceType> convertPerformances(final String list, final List<IPerformanceType> types) {
		final List<IPerformanceType> result = new LinkedList<IPerformanceType>();
		if (list.trim().equals("")) {
			return result;
		}
		for (final String abbreviation : list.split(" ")) {
			final IPerformanceType type = PerformanceListUtil.valueOf(abbreviation, types);
			if (type == null) {
				throw new IllegalStateException("Unkown performance type (" + abbreviation + ")");
			}
			
			result.add(type);
		}
		
		return result;
	}

	private List<ICourseType> convertCourseTypes(final String list,
			final List<ICourseType> types) {
		final List<ICourseType> result = new LinkedList<ICourseType>();
		if (list.trim().equals("")) {
			return result;
		}
		for (final String abbreviation : list.split(" ")) {
			final ICourseType type = CourseListUtil.valueOf(abbreviation, types);
			if (type == null) {
				throw new IllegalStateException("Unkown course type (" + abbreviation + ")");
			}
			
			result.add(type);
		}
		
		return result;
	}

	private ICourse convert(final XmlCourse course, final IStudyRegulations regulations)
	{
		final ICourse result = factory.createCourse();
		result.setName(course.getName());
		result.setECTS(course.getECTS());
		result.setHoursPerWeek(course.getHoursPerWeek());
		result.setCourseNumber(course.getCourseNumber());
		result.setExamNumber(course.getExamNumber());
		result.setGradedPerformance(convertPerformances(course.getGradedPerformance(), regulations.getPerformanceTypes()));
		result.setUngradedPerformance(convertPerformances(course.getUngradedPerformance(), regulations.getPerformanceTypes()));
		result.setType(convertCourseTypes(course.getType(), regulations.getCourseTypes()));
		return result;
	}

	private IModule convert(final XmlModule module, final IStudyRegulations regulations)
	{
		final IModule result = factory.createModule();
		result.setName(module.getName());

		final List<ICourse> courses = new LinkedList<ICourse>();
		for (XmlCourse course : module.getCourses()) {
			courses.add(convert(course, regulations));
		}
		result.setCourses(courses);
		return result;
	}

	private IElectiveCoursesArea convert(final XmlIndividualModule individualModule)
	{
		final IElectiveCoursesArea result = factory.createElectiveCoursesArea();
		result.setName(individualModule.getName());
		result.setECTS(individualModule.getECTS());
		result.setHoursPerWeek(individualModule.getHoursPerWeek());

		return result;
	}
	
	private IElectiveModulesArea convert(final XmlSelectableModuleLists selectableModuleLists, final IStudyRegulations regulations)
	{
		final IElectiveModulesArea result = factory.createElectiveModulesArea();
		result.setName(selectableModuleLists.getName());
		result.setPickCount(selectableModuleLists.getPickCount());

		final List<IModuleList> moduleLists = new LinkedList<IModuleList>();
		for (XmlModuleList moduleList : selectableModuleLists.getModuleLists()) {
			moduleLists.add(convert(moduleList, regulations));
		}
		result.setModuleLists(moduleLists);
		return result;
	}
	
	private IModuleList convert(final XmlModuleList list, final IStudyRegulations regulations)
	{
		final IModuleList result = factory.createModuleList();
		result.setName(list.getName());
		final List<IModuleListItem> modules = new LinkedList<IModuleListItem>();
		for (IXmlModuleListItem item : list.getModules()) {
			if (item instanceof XmlSelectableModuleLists) {
				modules.add(convert((XmlSelectableModuleLists)item, regulations));
				continue;
			}
			if (item instanceof XmlIndividualModule) {
				modules.add(convert((XmlIndividualModule)item));
				continue;
			}
			if (item instanceof XmlModule) {
				modules.add(convert((XmlModule)item, regulations));
				continue;
			}
		}
		
		result.setModules(modules);
		return result;
	}
	
	protected static List<ICourseType> convertCourseTypes(final List<XmlCourseTypeDescriptor> list) {
		final List<ICourseType> result = new LinkedList<ICourseType>();
		for (final XmlCourseTypeDescriptor descriptor : list) {
			GCCore.getFactory().createCourseType(result, descriptor.getId(), descriptor.getName());
		}
		
		return result;
	}
	
	protected static List<IPerformanceType> convertExamTypes(final List<XmlExaminationTypeDescriptor> list) {
		final List<IPerformanceType> result = new LinkedList<IPerformanceType>();
		for (final XmlExaminationTypeDescriptor descriptor : list) {
			GCCore.getFactory().createPerformanceType(result, descriptor.getId(), descriptor.getName());
		}
		
		return result;
	}
	
	@Override
	public IStudyRegulations readStudyRegulations(final IUniversity university, final String degree, final String course, final String version) {
		final String filename = university.getName() + File.separator + degree + File.separator + course + File.separator + version;
		return readStudyRegulations(getRegulationsFilename(filename));
	}

	@Override
	public IStudyRegulations readStudyRegulations(final String filename) {
		try {
			final IStudyRegulations result = factory.createStudyRegulations();
			final XmlStudyRegulationsDescription description = XmlStudyRegulationsDescription.createFromXML(filename);

			result.setUniversity(readUniversity(getUniversityFilename(description.getUniversity())));
			result.setDegree(description.getDegree());

			result.setCourseTypes(convertCourseTypes(description.getCourseTypes()));
			result.setPerformanceTypes(convertExamTypes(description.getExamTypes()));
			result.setModuleList(convert(description.getModuleList(), result));

			// TODO ...
			
			return result;
		}
		catch (final Exception e) {
			throw new PluginException("Cannot read regulations", e);
		}
	}

	@Override
	public List<String> getUniversities() {
		final List<String> result = new ArrayList<String>();
		final File importerDirectory = new File(root + File.separator + DIRUNIVERSITY);
		final FilenameFilter filter = new FilenameFilter() {
            @Override
			public boolean accept(final File dir, final String name) {
                return name.endsWith(EXTUNIVERSITY);
            }
        };

        final File[] fileList = importerDirectory.listFiles(filter);
        for (File file : fileList) {
        	if (file.isDirectory()) {
        		continue;
        	}
        	final String filename = file.getName();
        	final String universityName = filename.substring(0, filename.lastIndexOf('.'));
        	result.add(universityName);
        }
        return result;
	}

	@Override
	public List<String> getDegrees(final String university) {
		final String path = root + File.separator + DIRUNIVERSITY + File.separator + university;
		return getDirectories(path);
	}

	@Override
	public List<String> getCourses(final String university, final String degree) {
		final String path = root + File.separator + DIRUNIVERSITY + File.separator + university + File.separator + degree;
		return getDirectories(path);
	}

	@Override
	public List<String> getSemesters(final String university, final String degree, final String course) {
		
		final String path = root + File.separator + DIRUNIVERSITY + File.separator + university + File.separator + degree + File.separator + course;
		return getFiles(path, EXTSPODESCRIPTION);
	}
	
	protected List<String> getDirectories(final String root) {
		final List<String> result = new ArrayList<String>();
		final File importerDirectory = new File(root);

        final File[] fileList = importerDirectory.listFiles();
        for (File file : fileList) {
        	if (!file.isDirectory()) {
        		continue;
        	}
        	if (file.getName().startsWith(".")) {
        		continue;
        	}
        	result.add(file.getName());
        }
        return result;
	}

	protected List<String> getFiles(final String root, final String extension) {
		final List<String> result = new ArrayList<String>();
		final File importerDirectory = new File(root);

        final File[] fileList = importerDirectory.listFiles();
        for (File file : fileList) {
        	if (file.isDirectory()) {
        		continue;
        	}
        	if (!file.getName().endsWith(extension)) {
        		continue;
        	}
        	
        	final String name = file.getName().substring(0, file.getName().length() - extension.length());
        	result.add(name);
        }
        return result;
	}

	@Override
	public IStudyRegulations readStudyRegulations(final ICourseOfStudiesIdentifier id) {
		return readStudyRegulations(id.getUniversity(), id.getDegree(), id.getCourseOfStudies(), id.getVersionOfRegulations());
	}

	@Override
	public String getName() {
		return "Study-Regulation-Description-XML (SRDX)";
	}

	@Override
	public String getVersion() {
		return "0.9.0";
	}

	@Override
	public Map<String, List<String>> getLoadableFileExtensions() {
		return extensions;
	}

	@Override
	public Map<String, List<String>> getWriteableFileExtensions() {
		return extensions;
	}

	@Override
	public void setApplicationDirectory(final String directory) {
		root = directory;
	}

}
